﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sunny.Lib.CustomeAttribute;
using System.Diagnostics;
using System.Reflection;
using System.Collections;

namespace Sunny.Lib
{
    public class ConvertHelper
    {
        public static Encoding GetEncodingByName(string encodingStr)
        {
            Encoding encoding = Encoding.ASCII;

            switch (encodingStr.ToUpper())
            {
                case "UTF-8":
                case "UTF8":
                    encoding = Encoding.UTF8;
                    break;
                case "UNICODE":
                    encoding = Encoding.Unicode;
                    break;
                case "GBK":
                case "CP936":
                    encoding = Encoding.GetEncoding("gb2312");
                    break;
                default:
                    break;
            }

            return encoding;
        }

        /// <summary>
        /// Generate a object of T type from row.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="row"></param>
        /// <param name="attributeTypes"></param>
        /// <returns></returns>
        public static T GetEntityFromSPResult<T>(System.Data.DataRow row, List<Type> attributeTypes = null)
            where T : new()
        {
            if (row == null)
            {
                return default(T);
            }

            T t = new T();

            Type type = typeof(T);
            System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public;
            System.Reflection.PropertyInfo[] properties = type.GetProperties(flags);
            (from p in properties select p).ToList().ForEach(p =>
            {
                if (attributeTypes != null)
                {
                    if (!CustomeAttributeHelper.IsDefineAtLeastOne(p, attributeTypes))
                    {
                        object value = ChangeType(p.PropertyType, row[p.Name]);
                        p.SetValue(t,
                            value,
                            null);
                    }
                }
                else
                {
                    object value = ChangeType(p.PropertyType, row[p.Name]);
                    p.SetValue(t,
                        value,
                        null);
                }
            });

            return t;
        }

        /// <summary>
        /// Compare two same type object, and get the properties with different values between two object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="oldOne"></param>
        /// <param name="newOne"></param>
        /// <param name="isChanged">if no any difference, false, otherwise true</param>
        /// <param name="change">true:change oldOne with newOne if diff</param>
        /// <returns>different parts</returns>
        public static string GetDiff<T>(T oldOne, T newOne, out bool isChanged, bool change = false)
        {
            StringBuilder sb = new StringBuilder();

            Type t = typeof(T);
            PropertyInfo[] properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            isChanged = false;
            foreach (var p in properties)
            {
                if (p.GetCustomAttributes(typeof(ExcludeLogAttribute), false).Count() == 0)
                {
                    object newValue = p.GetValue(newOne, null);
                    object oldValue = p.GetValue(oldOne, null);
                    if (newValue == null)
                    {
                        if (oldValue != null)
                        {
                            sb.AppendFormat("{0}值由[{1}]改成[null];", p.Name, oldValue.ToString());

                            if (change)
                            {
                                try
                                {
                                    p.SetValue(oldOne, newValue, null);
                                }
                                catch (Exception ex)
                                {
                                    Logger.LogEvent(ex.Message, EventLevel.Error, ex.StackTrace);
                                }
                            }
                            if (isChanged == false)
                            {
                                isChanged = true;
                            }
                        }
                    }

                    else
                    {
                        bool flagDiff = false;
                        Type valueType = newValue.GetType();
                        string typeName = valueType.FullName;
                        if (valueType.IsPrimitive || typeName.Equals("System.String"))
                        {
                            if (!newValue.Equals(oldValue))
                            {
                                flagDiff = true;
                            }
                        }
                        else if (valueType.IsGenericType)
                        {
                            Type genericTypeDefinition = valueType.GetGenericTypeDefinition();
                            if (genericTypeDefinition == typeof(System.Nullable<>))
                            {
                                if (!newValue.Equals(oldValue))
                                {
                                    flagDiff = true;
                                }
                            }
                            else
                            {
                                // TODO:
                            }
                        }
                        else if (valueType.IsClass)
                        {
                            // TODO:
                        }
                        else
                        {
                            switch (valueType.FullName)
                            {
                                case "System.DateTime":
                                    if (oldValue != null)
                                    {
                                        flagDiff = (DateTime.Compare((DateTime)newValue, (DateTime)oldValue) != 0);
                                    }
                                    else
                                    {
                                        flagDiff = true;
                                    }
                                    break;
                                default:
                                    // TODO:
                                    break;
                            }
                        }

                        if (flagDiff)
                        {
                            sb.AppendFormat("{0}值由[{1}]改成[{2}];", p.Name, oldValue == null ? "null" : oldValue.ToString(), newValue.ToString());
                            if (change)
                            {
                                try
                                {
                                    p.SetValue(oldOne, newValue, null);
                                }
                                catch (Exception ex)
                                {
                                    Logger.LogEvent(ex.Message, EventLevel.Error, ex.StackTrace);
                                }
                            }
                            if (isChanged == false)
                            {
                                isChanged = true;
                            }
                        }
                    }
                }
            }

            string logContent = sb.ToString();
            if (isChanged)
            {
                logContent = logContent.TrimEnd(';');
            }
            return logContent;
        }

        public static M EntityToModel<E, M>(E entity)
            where E : new()
            where M : new()
        {
            M model = new M();

            if (entity == null)
            {
                return model;
            }

            Type eType = typeof(E);
            Type mType = typeof(M);
            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
            PropertyInfo[] eProperties = eType.GetProperties(flags);
            PropertyInfo[] mProperties = mType.GetProperties(flags);

            foreach (var mP in mProperties)
            {
                var tt = mP.Attributes;
                var realNameAttributes = mP.GetCustomAttributes(typeof(ColumnNameAttribute), false);
                string pName = mP.Name;
                object pValue = null;
                foreach (var attr in realNameAttributes)
                {
                    ColumnNameAttribute realNameAttribute = attr as ColumnNameAttribute;
                    if (realNameAttribute != null)
                    {
                        if (!string.IsNullOrEmpty(realNameAttribute.ColumnName))
                        {
                            pName = realNameAttribute.ColumnName;                    // If property name has alias, replace with it.
                            break;
                        }
                    }
                }

                // Set model object property value with the value that find in entity object.
                PropertyInfo eP = eProperties.Where((pr) => pr.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                if ((eP != null))
                {
                    pValue = eP.GetValue(entity, null);

                    try
                    {
                        mP.SetValue(model, pValue, null);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogEvent(ex.Message, EventLevel.Error, ex.StackTrace);
                    }
                }
            }

            return model;
        }

        public static void ModelToEntity<E, M>(M model, E entity)
            where E : new()
            where M : new()
        {
            if (entity == null)
                entity = new E();

            Type eType = typeof(E);
            Type mType = typeof(M);
            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
            PropertyInfo[] eProperties = eType.GetProperties(flags);
            PropertyInfo[] mProperties = mType.GetProperties(flags);

            foreach (var mP in mProperties)
            {
                var realNameAttributes = mP.GetCustomAttributes(typeof(ColumnNameAttribute), false);
                string pName = mP.Name;                      // Property name
                object pValue = mP.GetValue(model, null);    // Property value
                foreach (var attr in realNameAttributes)
                {
                    ColumnNameAttribute realNameAttribute = attr as ColumnNameAttribute;
                    if (realNameAttribute != null)
                    {
                        if (!string.IsNullOrEmpty(realNameAttribute.ColumnName))
                        {
                            pName = realNameAttribute.ColumnName;                    // If property name has alias, replace with it.
                            break;
                        }
                    }
                }

                // Set entity object property value with the value that find in model object.
                PropertyInfo eP = eProperties.Where((pr) => pr.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                if ((eP != null))
                {
                    try
                    {
                        eP.SetValue(entity, pValue, null);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogEvent(ex.Message, EventLevel.Error, ex.StackTrace);
                    }
                }
            }

        }

        public static M EntityToModelWithComplexProperty<E, M>(E entity)
        {
            Type eType = typeof(E);
            Type mType = typeof(M);

            M model = (M)CreateComplexModel(mType);

            if (entity == null)
            {
                return model;
            }

            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
            PropertyInfo[] eProperties = eType.GetProperties(flags);
            PropertyInfo[] mProperties = mType.GetProperties(flags);

            foreach (var mP in mProperties)
            {
                var realNameAttributes = mP.GetCustomAttributes(typeof(ColumnNameAttribute), false);
                string pName = mP.Name;
                object pValue = null;
                foreach (var attr in realNameAttributes)
                {
                    ColumnNameAttribute realNameAttribute = attr as ColumnNameAttribute;
                    if (realNameAttribute != null)
                    {
                        if (!string.IsNullOrEmpty(realNameAttribute.ColumnName))
                        {
                            pName = realNameAttribute.ColumnName;                    // If property name has alias, replace with it.
                            break;
                        }
                    }
                }

                // Set model object property value with the value that find in entity object.
                PropertyInfo eP = eProperties.Where((pr) => pr.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                if ((eP != null))
                {
                    object ePValue = eP.GetValue(entity, null);
                    pValue = internalConvert(ePValue, eP.PropertyType, mP.PropertyType, typeof(M), model);

                    try
                    {
                        mP.SetValue(model, pValue, null);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogEvent(ex.Message, EventLevel.Error, ex.StackTrace);
                    }
                }
            }

            return model;
        }

        public static void ModelToEntityWithComplexProperty<E, M>(M model, E entity)
            where E : new()
            where M : new()
        {
            if (entity == null)
                entity = new E();

            Type eType = typeof(E);
            Type mType = typeof(M);
            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
            PropertyInfo[] eProperties = eType.GetProperties(flags);
            PropertyInfo[] mProperties = mType.GetProperties(flags);

            foreach (var mP in mProperties)
            {
                var realNameAttributes = mP.GetCustomAttributes(typeof(ColumnNameAttribute), false);
                string pName = mP.Name;                      // Property name
                object mPValue = mP.GetValue(model, null);    // Property value
                foreach (var attr in realNameAttributes)
                {
                    ColumnNameAttribute realNameAttribute = attr as ColumnNameAttribute;
                    if (realNameAttribute != null)
                    {
                        if (!string.IsNullOrEmpty(realNameAttribute.ColumnName))
                        {
                            pName = realNameAttribute.ColumnName;                    // If property name has alias, replace with it.
                            break;
                        }
                    }
                }

                // Set entity object property value with the value that find in model object.
                PropertyInfo eP = eProperties.Where((pr) => pr.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                if ((eP != null))
                {
                    var pValue = internalConvert(mPValue, mP.PropertyType, eP.PropertyType, typeof(E), entity);

                    try
                    {
                        eP.SetValue(entity, pValue, null);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogEvent(ex.Message, EventLevel.Error, ex.StackTrace);
                    }
                }
            }

        }

        public static object CreateComplexModel(Type modelType)
        {
            Type type = modelType;

            if (modelType.IsGenericType)
            {
                Type genericTypeDefinition = modelType.GetGenericTypeDefinition();
                if (genericTypeDefinition == typeof(IDictionary<,>))
                {
                    type = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments());
                }
                else if (((genericTypeDefinition == typeof(IEnumerable<>)) || (genericTypeDefinition == typeof(ICollection<>))) || (genericTypeDefinition == typeof(IList<>)))
                {
                    type = typeof(List<>).MakeGenericType(
                    modelType.GetGenericArguments());
                }
            }
            return Activator.CreateInstance(type);
        }

        #region Private methods
        /// <summary>
        /// Convert value(fValue) with type(typeFrom) to another one with type(typeTo).
        /// </summary>
        /// <param name="fValue">Convert from</param>
        /// <param name="typeFrom">The type convert from</param>
        /// <param name="typeTo">The type convert to</param>
        /// <param name="stopLoopType">if class A, Clas B, A is a property of B, B is also a property of A, loop call internalConvert and not stop</param>
        /// <returns>Value after convertation</returns>
        private static object internalConvert(object fValue, Type typeFrom, Type typeTo, Type stopLoopType, object stopLoopValue)
        {
            if (typeTo == stopLoopType)
            {
                return stopLoopValue;
            }

            if (fValue == null)
            {
                return fValue;
            }

            // 1. Simple type.
            if (typeTo.IsPrimitive || typeTo.FullName.Equals("System.String"))
            {
                return fValue;
            }
            // 2. Generic type
            else if (typeTo.IsGenericType)
            {
                Type genericTypeDefinition = typeTo.GetGenericTypeDefinition();
                if (genericTypeDefinition == typeof(System.Nullable<>))
                {
                    return fValue;
                }
                else
                {
                    var toValue = CreateComplexModel(typeTo);
                    Type typeFrom1 = typeFrom.GetGenericArguments()[0];
                    Type typeTo1 = typeTo.GetGenericArguments()[0];

                    var items = (fValue as IEnumerable).GetEnumerator();
                    while (items.MoveNext())
                    {
                        object item = CreateComplexModel(typeTo);
                        item = internalConvert(items.Current, typeFrom1, typeTo1, stopLoopType, stopLoopValue);
                        (toValue as IList).Add(item);

                    }
                    return toValue;
                }
            }
            // 3. Class
            else if (typeTo.IsClass)
            {
                object toValue = CreateComplexModel(typeTo);

                BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
                PropertyInfo[] fProperties = typeFrom.GetProperties(flags);
                PropertyInfo[] tProperties = typeTo.GetProperties(flags);

                foreach (var tP in tProperties)
                {
                    var realNameAttributes = tP.GetCustomAttributes(typeof(ColumnNameAttribute), false);
                    string pName = tP.Name;
                    object pValue = null;
                    foreach (var attr in realNameAttributes)
                    {
                        ColumnNameAttribute realNameAttribute = attr as ColumnNameAttribute;
                        if (realNameAttribute != null)
                        {
                            if (!string.IsNullOrEmpty(realNameAttribute.ColumnName))
                            {
                                pName = realNameAttribute.ColumnName;                    // If property name has alias, replace with it.
                                break;
                            }
                        }
                    }

                    // Set model object property value with the value that find in entity object.
                    PropertyInfo fP = fProperties.Where((pr) => pr.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                    if ((fP != null))
                    {
                        object fPValue = fP.GetValue(fValue, null);
                        pValue = internalConvert(fPValue, fP.PropertyType, tP.PropertyType, stopLoopType, stopLoopValue);

                        try
                        {
                            tP.SetValue(toValue, pValue, null);
                        }
                        catch (Exception ex)
                        {
                            Logger.LogEvent(ex.Message, EventLevel.Error, ex.StackTrace);
                        }
                    }
                }
                return toValue;
            }
            else
            {
                return fValue;
            }
        }

        public static object ChangeType(Type type, object value)
        {
            if ((value == null) && type.IsGenericType)
            {
                return Activator.CreateInstance(type);
            }
            if (value == null)
            {
                return null;
            }
            if (type == value.GetType())
            {
                return value;
            }
            if (type.IsEnum)
            {
                if (value is string)
                {
                    return Enum.Parse(type, value as string);
                }
                return Enum.ToObject(type, value);
            }
            if (!type.IsInterface && type.IsGenericType)
            {
                Type type1 = type.GetGenericArguments()[0];
                object obj1 = ChangeType(type1, value);
                return Activator.CreateInstance(type, new object[] { obj1 });
            }
            if ((value is string) && (type == typeof(Guid)))
            {
                return new Guid(value as string);
            }
            if ((value is string) && (type == typeof(Version)))
            {
                return new Version(value as string);
            }
            if (!(value is IConvertible))
            {
                return value;
            }
            if (value.ToString() == string.Empty)
            {
                value = null;
                return value;
            }

            return Convert.ChangeType(value, type);
        }
        #endregion
    }
}
